#include "Logic.h"

//@author A0105511Y
const string Logic::FEEDBACK_ADD = "Added: ";
const string Logic::FEEDBACK_SEARCH = "Search results displayed";
const string Logic::FEEDBACK_TODAY = "Today's tasks displayed";
const string Logic::FEEDBACK_DELETE = "Deleted: ";
const string Logic::FEEDBACK_SORT_BY_TIME = "Added: ";
const string Logic::FEEDBACK_MODIFYING = "Modifying - Please enter the new task details";
const string Logic::FEEDBACK_MODIFIED = "Modified: ";
const string Logic::FEEDBACK_DONE = "Done: ";
const string Logic::FEEDBACK_UNDO = "Last command undone";
const string Logic::FEEDBACK_ALL = "All tasks displayed";

const string Logic::ERRORMESSAGE_INDEX_NOT_VALID = "Index not valid";
const string Logic::ERRORMESSAGE_NOT_MODIFYING = "You are not modifying a task";
const string Logic::ERRORMESSAGE_INVALID_COMMAND = "Invalid Command";

Logic::Logic(void) : logger(Logger::getInstance()) {
	displayToday();
	isModifying = false;
}

//@author A0096950U
string Logic::getFeedback() {
	return currentDisplay.getFeedback();
}

vector<Task> Logic::getMainDisplay() {
	return currentDisplay.getMainDisplay();
}

vector<Task> Logic::getDoneDisplay() {
	return currentDisplay.getDoneDisplay();
}

//@author A0105511Y
void Logic::searchTask(string keyword) {	
	currentDisplay.setToSearchResult();
	currentDisplay.setCurrentDisplay(storage.search(keyword));
	currentDisplay.setFeedback(FEEDBACK_SEARCH);
	currentDisplay.setSearchKeyword(keyword);
}

void Logic::displayToday() {
	currentDisplay.setToTodayTask();
	currentDisplay.setCurrentDisplay(storage.displayToday());
	currentDisplay.setFeedback(FEEDBACK_TODAY);
}

void Logic::addTask(string taskInString) {	
	Task newTask = parser.parseTask(taskInString);
	newTask.setStatus(TASK_ACTIVE);
	string newTaskID = storage.addTask(newTask);
	newTask.setID(newTaskID);
	Action newAction(COMMAND_ADD, newTask);
	history.addAction(newAction);
	displayToday();

	setFeedback(COMMAND_ADD, newTask);
}

void Logic::deleteTask(int taskIndex) {
	assert(taskIndex > 0);
	assert(taskIndex <= currentDisplay.getMainDisplaySize());

	Task currentTask = currentDisplay.getMainDisplayTask(taskIndex);

	currentDisplay.remove(taskIndex);

	currentTask.setStatus(TASK_ACTIVE); //for the purpose of updating status if this command is undo later
	string taskID = currentTask.getID();
	storage.remove(taskID);
	Task newTask = storage.retrieve(taskID);
	Action newAction(COMMAND_DELETE, newTask, currentTask);
	history.addAction(newAction);

	setFeedback(COMMAND_DELETE, currentTask);
}

string Logic::getTargetTask(int taskIndex) {
	assert(taskIndex > 0);
	assert(taskIndex <= currentDisplay.getMainDisplaySize());

	Task targetTask = currentDisplay.getMainDisplayTask(taskIndex);

	tempStore = new Task(targetTask);
	tempStore->setStatus(TASK_ACTIVE); //for the purpose of updating status if the modification is undone

	string stringToReturn = Utilities::STRING_TO + Utilities::STRING_WHITESPACE; 
	stringToReturn = stringToReturn + parser.reverseParse(targetTask);
	setFeedback(COMMAND_MODIFY, dummyTask);
	return stringToReturn;
}

void Logic::modifyTask(string newTaskInString) {
	Task newTask = parser.parseTask(newTaskInString);
	currentDisplay.remove(tempStore->getID());
	newTask.setID(tempStore->getID());
	newTask.setStatus(tempStore->getStatus());

	storage.modify(newTask);

	Task previousTask(*tempStore);

	Action newAction(COMMAND_MODIFY, newTask, previousTask);
	history.addAction(newAction);
	currentDisplay.push_back(newTask);
	currentDisplay.sortDisplay();

	setFeedback(COMMAND_TO, newTask);
}

void Logic::doneTask(int taskIndex) {
	Task currentTask = currentDisplay.getMainDisplayTask(taskIndex);
	currentTask.setStatus(TASK_ACTIVE); //for the purpose of updating status if this command is undone later
	string taskID = currentTask.getID();
	storage.setStatus(taskID, TASK_DONE);
	Task newTask = storage.retrieve(taskID);
	Action newAction(COMMAND_DONE, newTask, currentTask);
	history.addAction(newAction);
	currentDisplay.setToDone(taskIndex);
	setFeedback(COMMAND_DONE, currentTask);
}

void Logic::undoLastCommand() {
	Action lastAction = history.undoAction();
	CommandType lastCommand = lastAction.getCommandType();

	if (lastCommand == COMMAND_ADD) {
		storage.removeLast();
		currentDisplay.remove(lastAction.getCurrentTask().getID());
	} else if (lastCommand == COMMAND_DELETE) {
		Task taskToRecover = lastAction.getPreviousTask();
		storage.unremove(taskToRecover.getID());
		currentDisplay.push_back(taskToRecover);
		currentDisplay.sortDisplay();
	} else if (lastCommand == COMMAND_MODIFY) {
		Task taskToRecover = lastAction.getPreviousTask();
		storage.modify(taskToRecover);
		currentDisplay.modify(taskToRecover);
	} else if (lastCommand == COMMAND_DONE) {
		Task taskToRecover = lastAction.getPreviousTask();
		storage.undone(taskToRecover.getID());
		currentDisplay.undone(taskToRecover.getID());
	}

	currentDisplay.sortDisplay();
	setFeedback(COMMAND_UNDO, dummyTask);
}

string Logic::processUserCommand(string userCommand) {

	string firstWord = Utilities::extractFirstWord(userCommand);

	CommandType command = determineCommandType(firstWord);

	switch(command) {
	case COMMAND_ADD:
		isModifying = false;
		addTask(userCommand);
		break;

	case COMMAND_DELETE:
		isModifying = false;
		deleteTask(stoi(userCommand));
		break;

	case COMMAND_MODIFY:
		isModifying = true;
		return getTargetTask(stoi(userCommand));

	case COMMAND_TO:
		if (isModifying) {
			modifyTask(userCommand);
			isModifying = false;
		}
		else {
			throw runtime_error(ERRORMESSAGE_NOT_MODIFYING);
		}

		break;

	case COMMAND_DONE:
		isModifying = false;
		doneTask(stoi(userCommand));
		break;

	case COMMAND_SEARCH:
		isModifying = false;
		searchTask(userCommand);
		break;

	case COMMAND_ALL:
		isModifying = false;
		searchTask(Utilities::STRING_EMPTY);
		break;

	case COMMAND_TODAY:
		isModifying = false;
		displayToday();
		break;

	case COMMAND_UNDO:
		isModifying = false;
		undoLastCommand();
		break;

	case COMMAND_EXIT:
		return Utilities::STRING_EXIT;

	case COMMAND_INVALID:
		throw runtime_error(ERRORMESSAGE_INVALID_COMMAND);
	}

	return Utilities::STRING_EMPTY;
}

void Logic::setFeedback(CommandType currentCommand, Task currentTask) {
	string feedback;

	switch(currentCommand) {
	case COMMAND_ADD:
		feedback += FEEDBACK_ADD;
		feedback += currentTask.getDescription();
		break;

	case COMMAND_DELETE:
		feedback += FEEDBACK_DELETE;
		feedback += currentTask.getDescription();
		break;

	case COMMAND_MODIFY:
		feedback += FEEDBACK_MODIFYING;
		break;

	case COMMAND_TO:
		feedback += FEEDBACK_MODIFIED;
		feedback += currentTask.getDescription();
		break;

	case COMMAND_DONE:
		feedback += FEEDBACK_DONE;
		feedback += currentTask.getDescription();
		break;

	case COMMAND_SEARCH:
		feedback += FEEDBACK_SEARCH;
		break;

	case COMMAND_ALL:
		feedback += FEEDBACK_ALL;
		break;

	case COMMAND_TODAY:
		feedback += FEEDBACK_TODAY;
		break;

	case COMMAND_UNDO:
		feedback += FEEDBACK_UNDO;
		break;
	}

	currentDisplay.setFeedback(feedback);

	return;
}

bool Logic::isAPastDate(string targetDate) {
	assert(targetDate.size() == 8);
	time_t t = time(0); 
	char tmp[64]; 
	strftime( tmp, sizeof(tmp), "%d%m%Y",localtime(&t) );  
	string today(tmp);

	string targetDay = targetDate.substr(0,2);
	string targetMon = targetDate.substr(2,2);
	string targetYear = targetDate.substr(4,4);
	string todayDay = today.substr(0,2);
	string todayMon = today.substr(2,2);
	string todayYear = today.substr(4,4);

	if ((targetYear < todayYear) || (targetYear == todayYear && targetMon < todayMon) || (targetYear == todayYear && targetMon == todayMon && targetDay < todayDay)) {
		return true;
	}

	return false;
}

bool Logic::isToday(string targetDate) {
	time_t t = time(0); 
	char tmp[64]; 
	strftime( tmp, sizeof(tmp), "%d%m%Y",localtime(&t) );  
	string today(tmp);

	return today == targetDate;
}

bool Logic::isAPastTime(string targetTime) {
	time_t t = time(0); 
	char tmp[64]; 
	strftime( tmp, sizeof(tmp), "%H%M",localtime(&t) );

	string currentTime(tmp);

	return targetTime <= currentTime;
}

void Logic::updateStatus() {
	for (int i = 1; i <= currentDisplay.getMainDisplaySize(); i++) {

		if (currentDisplay.getMainDisplayTask(i).getStatus() == TASK_ACTIVE) {

			if (isAPastDate(currentDisplay.getMainDisplayTask(i).getDate())) {

				currentDisplay.setToOverdue(i);
				storage.setStatus(currentDisplay.getMainDisplayTask(i).getID(), TASK_OVERDUE);

			} else if (isToday(currentDisplay.getMainDisplayTask(i).getDate()) &&
						isAPastTime(currentDisplay.getMainDisplayTask(i).getStartTime())) {

					currentDisplay.setToOngoing(i);
					storage.setStatus(currentDisplay.getMainDisplayTask(i).getID(), TASK_ONGOING);
			}

		}

		if (currentDisplay.getMainDisplayTask(i).getStatus() == TASK_ONGOING) {

			if (isAPastDate(currentDisplay.getMainDisplayTask(i).getDate())) {
				currentDisplay.setToOverdue(i);
				storage.setStatus(currentDisplay.getMainDisplayTask(i).getID(), TASK_OVERDUE);
			} else if (isToday(currentDisplay.getMainDisplayTask(i).getDate()) &&
						isAPastTime(currentDisplay.getMainDisplayTask(i).getEndTime())) {

					currentDisplay.setToOverdue(i);
					storage.setStatus(currentDisplay.getMainDisplayTask(i).getID(), TASK_OVERDUE);
				
			}
		}
	}
}


//@author A0096950U
CommandType Logic::determineCommandType(string& command) {
	CommandType toReturn;
	Utilities::toLowerCase(command);

	if(command == Utilities::STRING_ADD) {
		toReturn = COMMAND_ADD;
	} else if(command == Utilities::STRING_DELETE) {
		toReturn = COMMAND_DELETE;
	} else if(command == Utilities::STRING_MODIFY) {
		toReturn = COMMAND_MODIFY;
	} else if(command == Utilities::STRING_TO) {
		toReturn = COMMAND_TO;
	} else if(command == Utilities::STRING_DONE) {
		toReturn = COMMAND_DONE;
	} else if(command == Utilities::STRING_SEARCH) {
		toReturn = COMMAND_SEARCH;
	} else if(command == Utilities::STRING_TODAY) {
		toReturn = COMMAND_TODAY;
	} else if(command == Utilities::STRING_UNDO) {
		toReturn = COMMAND_UNDO;
	} else if(command == Utilities::STRING_EXIT) {
		toReturn = COMMAND_EXIT;
	} else if(command == Utilities::STRING_ALL) {
		toReturn = COMMAND_ALL;
	} else {
		toReturn = COMMAND_INVALID;
	}

	return toReturn;
}

bool Logic::isValidInput(string userInput) {
	string firstWord = Utilities::extractFirstWord(userInput);

	CommandType command = determineCommandType(firstWord);

	bool toReturn;

	switch(command) {
	case COMMAND_INVALID:
		currentDisplay.setFeedback(ERRORMESSAGE_INVALID_COMMAND);
		toReturn = false;
		break;

	case COMMAND_TO:
		if(!isModifying) {
			currentDisplay.setFeedback(ERRORMESSAGE_NOT_MODIFYING);
			toReturn = false;
		} else {
			try {
				parser.parseTask(userInput);
				toReturn = true;
			} catch (runtime_error &e) {
				currentDisplay.setFeedback(e.what());
				toReturn = false;
			}
		}
		break;

	case COMMAND_ADD:
		try {
			parser.parseTask(userInput);
			toReturn = true;
		} catch (runtime_error &e) {
			currentDisplay.setFeedback(e.what());
			toReturn = false;
		}

		break;

	case COMMAND_DELETE:
	case COMMAND_MODIFY:
	case COMMAND_DONE:
		if(userInput == Utilities::STRING_EMPTY) {
			currentDisplay.setFeedback(ERRORMESSAGE_INDEX_NOT_VALID);
			toReturn = false;
		} else {
			try {
				int userIndex = stoi(userInput);
				if(userIndex > 0 && userIndex <= currentDisplay.getMainDisplaySize()) {
					toReturn = true;
				} else {
					currentDisplay.setFeedback(ERRORMESSAGE_INDEX_NOT_VALID);
					toReturn = false;
				}
			} catch (invalid_argument &e) {
				currentDisplay.setFeedback(ERRORMESSAGE_INDEX_NOT_VALID);
				toReturn = false;
			}
		}
		break;

	case COMMAND_SEARCH:
	case COMMAND_TODAY:
	case COMMAND_UNDO:
	case COMMAND_ALL:
	case COMMAND_EXIT:
		toReturn = true;
		break;
	}

	return toReturn;
}

string Logic::getSearchKeyword() const {
	return currentDisplay.getSearchKeyword();
}

bool Logic::isSearchResultDisplay() const {
	return currentDisplay.isSearchResultDisplay();
}